package com.kinyx.framework.crawler;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.bean.define.ICrawlerInfo;
import com.kinyx.framework.business.KURLConnection;
import com.kinyx.framework.file.KFileWriter;
import com.kinyx.framework.log.Log;
import com.kinyx.framework.log.Logger;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 抓取工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KCrawler {

	/** 日志对象 */
	private static final Log LOGGER = Logger.getLog(KCrawler.class);

	@ReturnNotNull
	private static CrawlerParam initCrawlerParam(@CanNotNull final CrawlerParam cp, @CanNull final ICrawlerInfo info) {
		cp.setIfLoggerIsNull(LOGGER);
		cp.setInfo(info);
		return cp;
	}

	@Throws(at = "KReader.read(InputStream, String)", value = "UnsupportedEncodingException", when = "new InputStreamReader(InputStream, String)")
	@Throws(at = "KReader.read(InputStream, String)", value = "IOException", when = "InputStreamReader.read()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "MalformedURLException", when = "new URL(String)")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.openConnection()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.connect()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "InputStream.getInputStream()")
	@Throws(at = "KFileUtils.checkDirectory(File, boolean)", value = "ErrorCode0.IsNotADirectory", when = "!File.isDirectory()")
	@Throws(at = "KFileUtils.createNewFile(String)", value = "IOException", when = "File.createNewFile()")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "UnsupportedEncodingException", when = "String.getBytes(String)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "FileNotFoundException", when = "new FileOutputStream(File)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "IOException", when = "FileChannel.write(ByteBuffer)")
	private static void crawl(@CanNotNull final CrawlerParam cp) {
		// 页面抓取信息
		final ICrawlerInfo info = cp.getInfo();
		// 开始日志
		cp.logBeforeExecution();
		// 读取页面
		String result = null;
		try {
			result = KURLConnection.GET(info.getRequestArgs());
		} catch (final Exception e) {
			// 异常日志
			cp.logAfterExecution(e);
			return;
		}
		// 写入文件
		try {
			KFileWriter.write(info.getFilePath(), info.getFileCharset(), result);
		} catch (final Exception e) {
			// 异常日志
			cp.logAfterExecution(e);
			return;
		}
		// 结束日志
		cp.logAfterExecution(null);
	}

	/**
	 * 抓取页面
	 * @param cp 抓取参数
	 * @param info 页面抓取URL信息
	 * @param thread true：开启线程；false：不开启线程；
	 * @return 开启线程的情况下返回线程对象，否则返回null。
	 */
	@Throws(at = "KReader.read(InputStream, String)", value = "UnsupportedEncodingException", when = "new InputStreamReader(InputStream, String)")
	@Throws(at = "KReader.read(InputStream, String)", value = "IOException", when = "InputStreamReader.read()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "MalformedURLException", when = "new URL(String)")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.openConnection()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.connect()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "InputStream.getInputStream()")
	@Throws(at = "KFileUtils.checkDirectory(File, boolean)", value = "ErrorCode0.IsNotADirectory", when = "!File.isDirectory()")
	@Throws(at = "KFileUtils.createNewFile(String)", value = "IOException", when = "File.createNewFile()")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "UnsupportedEncodingException", when = "String.getBytes(String)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "FileNotFoundException", when = "new FileOutputStream(File)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "IOException", when = "FileChannel.write(ByteBuffer)")
	@Tester("com.kinyx.framework.crawler.KCrawler_Tester_0_main")
	@Tester("com.kinyx.framework.crawler.KCrawler_Tester_0_thread")
	@ReturnNull("thread == false")
	public static Thread crawl(@CanNull final CrawlerParam cp, @CanNotNull final ICrawlerInfo info, @CanNotNull final boolean thread) {
		if (thread) {// 开启线程执行
			final Thread t = new Thread(() -> { crawl(initCrawlerParam(CrawlerParam.of(cp), info)); });
			t.start();
			return t;
		} else {// 不开启线程执行
			crawl(initCrawlerParam(CrawlerParam.of(cp), info));
			return null;
		}
	}

	/**
	 * 抓取页面
	 * @param cp 抓取参数
	 * @param infos 页面抓取信息
	 * @param maxThreads 最大同时进行的线程数（0或负值：不开启线程；正值：线程数；）
	 */
	@Throws(at = "KReader.read(InputStream, String)", value = "UnsupportedEncodingException", when = "new InputStreamReader(InputStream, String)")
	@Throws(at = "KReader.read(InputStream, String)", value = "IOException", when = "InputStreamReader.read()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "MalformedURLException", when = "new URL(String)")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.openConnection()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "URLConnection.connect()")
	@Throws(at = "KURLConnection.GET(GetArgs)", value = "IOException", when = "InputStream.getInputStream()")
	@Throws(at = "KFileUtils.checkDirectory(File, boolean)", value = "ErrorCode0.IsNotADirectory", when = "!File.isDirectory()")
	@Throws(at = "KFileUtils.createNewFile(String)", value = "IOException", when = "File.createNewFile()")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "UnsupportedEncodingException", when = "String.getBytes(String)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "FileNotFoundException", when = "new FileOutputStream(File)")
	@Throws(at = "KFileWriter.write(String, String, String)", value = "IOException", when = "FileChannel.write(ByteBuffer)")
	@Tester("com.kinyx.framework.crawler.KCrawler_Tester_1")
	public static void crawl(@CanNull final CrawlerParam cp, @CanNull final List<ICrawlerInfo> infos, @CanNotNull final int maxThreads) {
		if ((infos == null) || infos.isEmpty()) { return; }
		final CrawlerParam cpMain = initCrawlerParam(CrawlerParam.of(cp), null);
		// 开始日志
		cpMain.logBeforeExecution();
		if (maxThreads > 0) {// 开启线程执行
			// 线程队列
			final Queue<Thread> threads = new LinkedList<>();
			// 列表转队列
			infos.forEach(info -> { threads.add(new Thread(() -> { crawl(cpMain.subParam(info)); })); });
			// 一次启动最大线程数个线程
			while (!threads.isEmpty()) {
				final List<Thread> subThreads = new ArrayList<>();
				for (int i = 0; i < maxThreads; i++) {
					if (threads.isEmpty()) { break; }
					final Thread thread = threads.poll();
					// 加入列表
					subThreads.add(thread);
					// 启动线程
					thread.start();
				}
				// join线程
				subThreads.forEach(thread -> {
					try {
						thread.join();
					} catch (final InterruptedException e) {
						// 异常日志
						cpMain.logAfterExecution(e);
					}
				});
			}
		} else {// 不开启线程执行
			// 执行抓取
			infos.forEach(info -> { crawl(cpMain.subParam(info)); });
		}
		// 结束日志
		cpMain.logAfterExecution(null);
	}

}
